package org.jetbrains.exposed.v1.spring.boot.autoconfigure

import org.jetbrains.exposed.v1.core.DatabaseConfig
import org.jetbrains.exposed.v1.core.ResultRow
import org.jetbrains.exposed.v1.jdbc.selectAll
import org.jetbrains.exposed.v1.spring.boot.Application
import org.jetbrains.exposed.v1.spring.boot.DatabaseInitializer
import org.jetbrains.exposed.v1.spring.boot.tables.TestTable
import org.jetbrains.exposed.v1.spring.transaction.ExposedSpringTransactionAttributeSource
import org.jetbrains.exposed.v1.spring.transaction.SpringTransactionManager
import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.NoSuchBeanDefinitionException
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.autoconfigure.AutoConfigurations
import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.boot.test.context.TestConfiguration
import org.springframework.boot.test.context.runner.ApplicationContextRunner
import org.springframework.context.annotation.Bean
import org.springframework.scheduling.annotation.Async
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import org.springframework.transaction.interceptor.TransactionAttributeSource
import java.util.concurrent.CompletableFuture

@SpringBootTest(
    classes = [Application::class, ExposedAutoConfigurationTest.CustomDatabaseConfigConfiguration::class],
    properties = ["spring.datasource.url=jdbc:h2:mem:test", "spring.datasource.driver-class-name=org.h2.Driver"]
)
open class ExposedAutoConfigurationTest {

    @Autowired(required = false)
    private var springTransactionManager: SpringTransactionManager? = null

    @Autowired(required = false)
    private var databaseInitializer: DatabaseInitializer? = null

    @Autowired
    private var databaseConfig: DatabaseConfig? = null

    @Autowired
    private var transactionAttributeSource: TransactionAttributeSource? = null

    @Test
    fun `should initialize the database connection`() {
        assertNotNull(springTransactionManager)
    }

    @Test
    fun `should not create schema`() {
        assertNull(databaseInitializer)
    }

    @Test
    fun `database config can be overrode by custom one`() {
        val expectedConfig = CustomDatabaseConfigConfiguration.expectedConfig
        assertSame(databaseConfig, expectedConfig)
        assertEquals(
            expectedConfig.maxEntitiesToStoreInCachePerEntity,
            databaseConfig!!.maxEntitiesToStoreInCachePerEntity
        )
    }

    @Test
    fun testClassExcludedFromAutoConfig() {
        val contextRunner = ApplicationContextRunner().withConfiguration(
            AutoConfigurations.of(Application::class.java)
        )
        contextRunner.run { context ->
            assertThrows(NoSuchBeanDefinitionException::class.java) {
                context.getBean(DataSourceTransactionManagerAutoConfiguration::class.java)
            }
        }
    }

    @Test
    fun `load ExposedSpringTransactionAttributeSource`() {
        transactionAttributeSource?.let {
            assertEquals(ExposedSpringTransactionAttributeSource::class.java, it.javaClass)
        } ?: fail("TransactionAttributeSource bean not found")
    }

    @TestConfiguration
    open class CustomDatabaseConfigConfiguration {

        @Bean
        open fun customDatabaseConfig(): DatabaseConfig {
            return expectedConfig
        }

        companion object {
            val expectedConfig = DatabaseConfig {
                maxEntitiesToStoreInCachePerEntity = 777
            }
        }
    }
}

@SpringBootTest(
    classes = [Application::class],
    properties = [
        "spring.datasource.url=jdbc:h2:mem:test",
        "spring.datasource.driver-class-name=org.h2.Driver",
        "spring.exposed.generate-ddl=true",
        "spring.exposed.show-sql=true"
    ]
)
open class ExposedAutoConfigurationTestAutoGenerateDDL {

    @Autowired(required = false)
    private var springTransactionManager: SpringTransactionManager? = null

    @Autowired
    private lateinit var asyncService: AsyncExposedService

    @Test
    fun `should initialize the database connection`() {
        assertNotNull(springTransactionManager)
    }

    @Test
    @Transactional
    open fun `should create schema`() {
        assertEquals(0L, TestTable.selectAll().count())
    }

    @Test
    @Transactional
    @Disabled
    open fun `async service should be initialized properly`() {
        assertEquals(0, asyncService.allTestData().size)
    }

    @Test
    @Transactional
    @Disabled
    open fun `async service should be initialized properly with completableFuture`() {
        assertEquals(0, asyncService.allTestDataAsync().join().size)
    }
}

// See https://docs.spring.io/spring/docs/current/spring-framework-reference/integration.html#scheduling-annotation-support
@Transactional
@Service
@Async // if not put then allTestData() has no error. In all cases allTestDataAsync will have error
// Issue comes from TransactionAspectSupport, implementation changed between spring version 5.1.X and 5.2.0
open class AsyncExposedService {

    // if not using @EnableAsync, this method passes the test which should not
    open fun allTestData() = TestTable.selectAll().toList()

    // you need to put open otherwise @Transactional is not applied since spring plugin not applied (similar to maven kotlin plugin)
    open fun allTestDataAsync(): CompletableFuture<List<ResultRow>> = CompletableFuture.completedFuture(
        TestTable.selectAll().toList()
    )
}
